package hhpoll.service;

import hhpoll.dao.DAOFactory;
import hhpoll.dao.UserDAO;
import hhpoll.dao.UserRegistrationDAO;
import hhpoll.domain.User;
import hhpoll.domain.UserRegistration;
import hhpoll.service.context.RequestContext;
import hhpoll.validation.UserValidator;

import java.util.Calendar;
import java.util.Date;

import org.apache.log4j.Logger;
import org.hibernate.HibernateException;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.BindException;

@Transactional
public class UserServiceImpl implements UserService {

    private static Logger log = Logger.getLogger(UserServiceImpl.class);

    private DAOFactory daoFactory;
    private MailSender mailSender;
	
	public UserServiceImpl(DAOFactory daoFactory, MailSender mailSender) {
        this.daoFactory = daoFactory;
        this.mailSender = mailSender;
		log.debug("Creating UserServiceImpl: daoFactory="+daoFactory);
	}

	public User registerUser(User user) {
        log.info("Entering registerUser");
		// This introduces coupling to the Spring framework...oh well
		UserValidator validator = new UserValidator();
		BindException errors = new BindException(user, "user");
		validator.validate(user, errors);
		if (errors.hasErrors()) {
			// This is temporary.  We need to construct a better message 
			// based on the errors object.
			throw new ServiceException(errors.getMessage());
		}
		
        try {
            UserDAO userDao = daoFactory.createUserDAO();
            UserRegistrationDAO userRegistrationDao = daoFactory.createUserRegistrationDAO();
            
            // Check for an existing user with the same e-mail
            User existingUser = userDao.findByEmail(user.getEmail());
            if (existingUser != null)
            	throw new ServiceException("Account already exists for "+user.getEmail());
            
            // TODO: If there is an existing registration that is not expired, what
            // do we do?  I don't think it hurts to allow the user to log an additional
            // registration (maybe the e-mail got lost), as long as we prevent activation
            // of the account if there is already an existing account.  So for now, do
            // nothing.
            
            // Create a new UserRegistration based on the information passed in the User object
            UserRegistration registration = new UserRegistration();
            registration.setFirstName(user.getFirstName());
            registration.setLastName(user.getLastName());
            registration.setEmail(user.getEmail());
            registration.setAlias(user.getAlias());
            registration.setPassword(user.getPassword());
            
            // Initialize the create date
            registration.setCreateDate(new Date());
            
            // Assign a UUID to the registration.
            registration.setUuid(java.util.UUID.randomUUID().toString());
            
            // Create the registration
            registration = userRegistrationDao.create(registration);
            
            // Make sure we don't send back the real password!
            user.setPassword("XXXX");
            
            // Send an e-mail notification to the user that they have been registered with the system.
			SimpleMailMessage message = new SimpleMailMessage();
			message.setFrom("uVote <noreply@uVote.com>");
			message.setTo(registration.getEmail());
			message.setSubject("New User Registration");
			message.setText(registration.getUuid());
			message.setSentDate(new Date());
			mailSender.send(message);
            
            // return the user 
    		return user;
		} catch (HibernateException e) {
			// TODO: Remove coupling from Hibernate here.  Need a DAOException class.
			throw new ServiceException("Unable to create user: "+e.getMessage(), e);
		}
	}
	
	@Override
	public User activateUser(UserRegistration userRegistration) throws ServiceException {
		
		// validations
		if (userRegistration.getUuid() == null)
    		throw new ServiceException("Registration UUID not provided");
		
		if (userRegistration.getUuid().length() != 36)
    		throw new ServiceException("Invalid UUID provided (must be 36 characters)");
			
		// Get the registration from the database
        UserRegistrationDAO userRegistrationDao = daoFactory.createUserRegistrationDAO();
        userRegistration = userRegistrationDao.findByUUID(userRegistration.getUuid());
        
        // If the registration doesn't exist, throw an exception
        if (userRegistration == null)
    		throw new ServiceException("UUID not found");
        
        // Verify the registration is not older than 24 hours
        Calendar expireDate = Calendar.getInstance();
        expireDate.setTime(userRegistration.getCreateDate());
        expireDate.add(Calendar.HOUR, 24);
        if (new Date().after(expireDate.getTime()))
    		throw new ServiceException("Registration has expired.  Please register again.");
        
        // Populate the user based on the registration
        UserDAO userDao = daoFactory.createUserDAO();
        User newUser = new User();
        newUser.setFirstName(userRegistration.getFirstName());
        newUser.setLastName(userRegistration.getLastName());
        newUser.setAlias(userRegistration.getAlias());
        newUser.setEmail(userRegistration.getEmail());
        newUser.setPassword(userRegistration.getPassword());
        newUser.setCreateDate(new Date());
        
        // Check if the user has already successfully been created.
        User existingUser = userDao.findByEmail(newUser.getEmail());
        if (existingUser != null)
    		throw new ServiceException("User with e-mail ["+newUser.getEmail()+"] has already registered.");
        
        // Create the user
        userDao.create(newUser);
        
        // Make sure not to return the password.
        newUser.setPassword("XXXX");
        
        return newUser;
	}
	
	public User authenticate(String username, String password) {
		// Validate the input parameters
		if (username == null || username.length() == 0)
			throw new ServiceException("Username is required.");
		if (password == null || password.length() == 0)
			throw new ServiceException("Password is required.");
		
        log.info("username="+username);
        //log.info("given Password="+password);

        UserDAO userDao = daoFactory.createUserDAO();
        try {
            User theUser = userDao.findByEmail(username);
            
            // Validate the password is correct.
            if (theUser == null || theUser.getPassword() == null || !password.equals(theUser.getPassword()))
                throw new ServiceException("Access denied for user "+username+": wrong username/password");
            
            log.info("real Password="+theUser.getPassword());
            return theUser;
        } catch (HibernateException e) {
            throw new ServiceException("Access denied for user "+username+": wrong username/password");
        }
	}

    public User getUser(RequestContext ctxt, String username) {
        log.info("getUser: username="+username);
        log.info("getUser: context.username="+ctxt.getUsername());

		// Validate the input parameters
		if (username == null || username.length() == 0)
			throw new ServiceException("Username is required.");

        try {
            // Authenticate the user based on the RequestContext
            User theUser = authenticate(ctxt.getUsername(), ctxt.getPassword());
            if (!username.equals(theUser.getEmail()))
                throw new ServiceException("Not authorized to view another user's profile.");
            return theUser;
        } catch (HibernateException e) {
            throw new ServiceException("Unable to retrieve user: "+username);
        }
    }

}
